'use strict';

/**
 * 数据包格式定义
 *    头：
 *    0,        1,      2,      3,      7,          11,          13,     15
 *    flag,     code,   ack,    msgId,  reserved    version,    status, length
 *    byte      byte    byte    int32   int32       int16       int16   int32
 *    体：
 *    19.......
 * 
 * 注：从这里看来，这个数据包的定义不是很好，因为中间有过大的变动，
 *     为了适应这种变动，最好把版本号提到较前面的位置以便后来的数据格式兼容。
 *    
 */
const util = require('util');
const CryptoHelper = require('./CryptoHelper');


const VERSION = 1;
const HEAD_LENGTH = 19;

class Message {
    get flag() {
        return this._buffer.readUInt8(0)
    }
    set flag(value) {
        this._buffer.writeUInt8(value, 0);
    }
    get code() {
        return this._buffer.readUInt8(1)
    }
    set code(value) {
        this._buffer.writeUInt8(value, 1);
    }
    get ack() {
        return this._buffer.readInt8(2)
    }
    set ack(value) {
        this._buffer.writeInt8(value, 2);
    }
    get messageId() {
        return this._buffer.readUInt32BE(3);
    }
    set messageId(value) {
        this._buffer.writeUInt32BE(value, 3);
    }
    get reserved() {
        return this._buffer.readUInt32BE(7);
    }
    set reserved(value) {
        this._buffer.writeUInt32BE(value, 7);
    }
    get version() {
        return this._buffer.readUInt16BE(11);
    }
    set version(value) {
        this._buffer.writeUInt16BE(value, 7);
    }
    get status() {
        return this._buffer.readUInt16BE(13);
    }
    set status(value) {
        this._buffer.writeUInt16BE(value, 13);
    }
    get length() {
        return this._buffer.readInt32BE(15);
    }
    set length(value) {
        this._buffer.writeInt32BE(value, 15);
    }
    get encrypt() {
        return true;
    }
    set buffer(buffer) {
        if (!Buffer.isBuffer(buffer)) {
            throw new Error('aruments[0] is not buffer');
        }
        if (buffer.length < HEAD_LENGTH) {
            throw new Error('buffer error');
        }
        this._buffer = Buffer.from(buffer);
    }
    get buffer() {
        return this._buffer;
    }
    setOptions(opts) {
        this._buffer = new Buffer(HEAD_LENGTH);
        ['flag', 'code', 'ack', 'version', 'status', 'messageId', 'reserved', 'length']
            .forEach((key) => {
                if (opts.hasOwnerProperty(key))
                    this[key] = opts[key];
                else
                    this[key] = 0;
            });
    }
}

class JSONMessage extends Message {
    /**
     * @param arg   opts
     */
    constructor(opts) {
        var defaultOpts = {
            version: 1
        }
        var options = Object.assign({}, defaultOpts, opts);
        this.setOptions(options);
        this.setCryptoKey(opts.xorKey, opts.aseKey);
        options.body && this.setBody(options.body);
    }
    setCryptoKey(xorKey, aseKey) {
        this.xorCrypto = new CryptoHelper.XORCrypto(xorKey);
        this.aseCrypto = new CryptoHelper.ASECrypto(aseKey);
    }
    /**
     * get string body
     */
    getBody() {
        var body = Buffer.alloc(this.length);
        this.buffer.copy(body, HEAD_LENGTH);
        return Promise.resolve().then(() => this.crypto ? this.aseCrypto.decode(body) : body);
    }
    getBodyAsJson() {
        return this.getBody().then(body => JSON.parse(body));
    }
    setBody(body) {
        var data;
        if (util.isObject(body)) {
            // json object body
            data = Buffer.from(JSON.stringify(body));
        } else {///bit array||string||buffer
            data = Buffer.from(body);
        }
        return Promise.resolve()
            .then(() => this.crypto ? this.aseCrypto.encode(data) : data)
            .then(buff => {
                this.length = buff.length;
                this.buffer.length = HEAD_LENGTH + this.length;
                buff.copy(this.buffer, HEAD_LENGTH)
            })
    }
}

module.exports = {
    Message: Message,
    JSONMessage: JSONMessage
}